package cn.kk.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.kk.api.feign.CommonClient;
import cn.kk.api.feign.UserInfoClient;
import cn.kk.code.ErrorCodeEnum;
import cn.kk.constant.FileConstant;
import cn.kk.constant.RedisKeyConstant;
import cn.kk.model.auth.req.LoginReq;
import cn.kk.model.auth.req.RegisterReq;
import cn.kk.model.auth.vo.CaptchaVO;
import cn.kk.model.auth.vo.PayloadUserVO;
import cn.kk.model.auth.vo.TokenAuthVO;
import cn.kk.model.user.constant.UserSexEnum;
import cn.kk.model.user.constant.UserStateEnum;
import cn.kk.model.user.entity.UserInfoEntity;
import cn.kk.service.AuthService;
import cn.kk.utils.*;
import com.wf.captcha.base.Captcha;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: kk
 * @Date: 2023/2/12  17:13
 * @Version 1.0
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private UserInfoClient userInfoClient;
    @Resource
    private CommonClient commonClient;

    /**
     * 验证码生成
     * @return 验证码视图
     */
    @Override
    public CaptchaVO buildCaptcha() {
        // 生成uuid
        String uuid = UUID.randomUUID().toString();
        // 随机生成验证码
        Captcha captcha = CaptchaUtil.randomCaptcha();
        // 验证码图片
        String base64 = captcha.toBase64();
        // 答案
        String answer = captcha.text();
        log.info("[验证码]answer= {}, uuid= {}", answer, uuid);
        // 将答案存入缓存，设置1分钟过期时间
        stringRedisTemplate.opsForValue().set(RedisKeyConstant.CAPTCHA_KEY + uuid, answer, 1, TimeUnit.MINUTES);
        // 构建响应对象
        return new CaptchaVO(uuid, base64);
    }

    /**
     * 新用户注册
     * @param req 用户注册信息
     * @return 执行结果
     */
    @Override
    public boolean register(RegisterReq req) {
        // 校验验证码
        checkCaptcha(req.getRequestId(), req.getCaptchaAnswer());
        // 校验手机号和邮箱是否重复
        boolean phoneRepeat = userInfoClient.checkRepeat(req.getPhone(), null);
        ExceptionUtil.assertion(phoneRepeat, ErrorCodeEnum.USER_ERROR_A0155);
        boolean emailRepeat = userInfoClient.checkRepeat(null, req.getEmail());
        ExceptionUtil.assertion(emailRepeat, ErrorCodeEnum.USER_ERROR_A0154);
        // 加密密码
        String password = req.getPassword();
        String salt = BCrypt.gensalt();
        password = BCrypt.hashpw(password, salt);
        // 生成随机用户名
        String userName = RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER + RandomUtil.BASE_CHAR.toUpperCase(), 8);
        // 新增用户
        UserInfoEntity userInfoAdd = new UserInfoEntity();
        userInfoAdd.setUserName(userName);
        userInfoAdd.setPhone(req.getPhone());
        userInfoAdd.setEmail(req.getEmail());
        userInfoAdd.setPassword(password);
        userInfoAdd.setSalt(salt);
        userInfoAdd.setState(UserStateEnum.DISABLE.getCode());
        userInfoAdd.setSex(UserSexEnum.UNKNOWN.getCode());
        return userInfoClient.save(userInfoAdd);
    }

    /**
     * 用户登录
     * @param loginInfo 用户信息
     * @param request   请求对象
     * @return 认证信息
     */
    @Override
    public TokenAuthVO login(LoginReq loginInfo, HttpServletRequest request) {
        // 校验验证码
        checkCaptcha(loginInfo.getRequestId(), loginInfo.getCaptchaAnswer());
        // 记录用户IP
        String ipAddress = RequestUtil.getIpAddress(request);
        log.info("用户ip是:{}", ipAddress);
        // 验证账号密码
        UserInfoEntity user = userInfoClient.search(loginInfo.getAccount());
        ExceptionUtil.isNull(user, ErrorCodeEnum.USER_ERROR_A0201);
        String password = loginInfo.getPassword();
        boolean match = BCrypt.checkpw(password, user.getPassword());
        ExceptionUtil.assertion(!match, ErrorCodeEnum.USER_ERROR_A0210);
        return buildToken(user);
    }

    /**
     * 刷新token
     * @return token
     */
    @Override
    public TokenAuthVO refresh() {
        UserInfoEntity userInfo = commonClient.currentUser();
        return buildToken(userInfo);
    }

    /**
     * 校验验证码
     * @param requestId     请求id
     * @param captchaAnswer 验证码
     */
    private void checkCaptcha(String requestId, String captchaAnswer) {
        // 验证码后门，方便调试
        if ("ditto".equals(captchaAnswer)) {
            return;
        }
        // 通过请求id获取验证码
        String answer = stringRedisTemplate.opsForValue().get(RedisKeyConstant.CAPTCHA_KEY + requestId);
        // 删除验证码，防止反复利用
        stringRedisTemplate.delete(RedisKeyConstant.CAPTCHA_KEY + requestId);
        // 校验验证码
        ExceptionUtil.isNull(answer, ErrorCodeEnum.USER_ERROR_A0242);
        ExceptionUtil.assertion(!answer.equals(captchaAnswer), ErrorCodeEnum.USER_ERROR_A0240);
    }

    /**
     * 生成token视图
     * @param user 用户信息
     * @return token视图
     */
    private TokenAuthVO buildToken(UserInfoEntity user) {
        // 准备token载荷
        PayloadUserVO payload = BeanHelper.copyProperties(user, PayloadUserVO.class);
        String token = null;
        try {
            // 读取私钥
            String preKeyPath = Objects.requireNonNull(this.getClass().getClassLoader().getResource(FileConstant.PreKey)).getPath();
            // 生成token，有效期7天
            token = JwtUtils.generateTokenExpireInMinutes(payload, RSAUtil.readPrivateKey(preKeyPath), 60 * 24 * 7);
        } catch (Exception e) {
            log.error("[token签发异常]:" + e.getMessage(), e);
            ExceptionUtil.throwOut(ErrorCodeEnum.USER_ERROR_A0200);
        }
        TokenAuthVO tokenAuthVO = new TokenAuthVO();
        tokenAuthVO.setToken(token);
        tokenAuthVO.setUserName(user.getUserName());
        return tokenAuthVO;
    }
}
